/*
 * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
 *
 * Copyright (C) 2016-2023 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitee.wsl.coroutines.job.trigger.impl

import com.gitee.wsl.coroutines.job.exception.SchedulerException
import com.gitee.wsl.coroutines.job.trigger.IScheduleBuilder
import com.gitee.wsl.coroutines.job.trigger.SimpleScheduleBuilder
import com.gitee.wsl.coroutines.job.trigger.ISimpleTrigger
import com.gitee.wsl.coroutines.job.trigger.ISimpleTrigger.Companion.REPEAT_INDEFINITELY
import com.gitee.wsl.coroutines.job.trigger.ITrigger
import com.gitee.wsl.time.calendar.Calendar
import kotlin.time.Instant
import kotlin.time.DurationUnit
import kotlin.time.ExperimentalTime
import kotlin.time.toDuration

/**
 *
 *
 * A concrete `[com.helger.quartz.ITrigger]` that is used to
 * fire a `[com.helger.quartz.IJobDetail]` at a given moment in
 * time, and optionally repeated at a specified interval.
 *
 *
 * @see com.helger.quartz.ITrigger
 *
 * @see com.helger.quartz.ICronTrigger
 *
 * @see com.helger.quartz.TriggerUtils
 *
 * @author James House
 * @author contributions by Lieven Govaerts of Ebitec Nv, Belgium.
 */
@OptIn(ExperimentalTime::class)
class SimpleTrigger(
    override var repeatInterval: Long = 0,
    override var timesTriggered: Int = 0
) : AbstractTrigger<SimpleTrigger>(), ISimpleTrigger {

    @OptIn(ExperimentalTime::class)
    override var startTime: Instant? = null
        set(value) {
            val eTime: Instant? = endTime
            if (eTime != null && eTime < value!!)
                throw IllegalArgumentException("End time cannot be before start time")
            field = value
        }

    @OptIn(ExperimentalTime::class)
    override var endTime: Instant? = null
        set(value) {
            val sTime = startTime
            if (sTime != null &&  sTime > value!!)
                throw IllegalArgumentException("End time cannot be before start time")
            field = value
        }

    override var repeatCount: Int = 0
        /**
         *
         *
         * Set the the number of time the `SimpleTrigger` should repeat,
         * after which it will be automatically deleted.
         *
         *
         * @see .REPEAT_INDEFINITELY
         *
         * @exception IllegalArgumentException
         * if repeatCount is &lt; 0
         */
        set(repeatCount) {
            if (repeatCount < 0 && repeatCount != REPEAT_INDEFINITELY)
                throw IllegalArgumentException("Repeat count must be >= 0, use the " + "constant REPEAT_INDEFINITELY for infinite.")

            field = repeatCount
        }

    override var nextFireTime: Instant? = null

    override var previousFireTime: Instant? = null

    override fun validateMisfireInstruction(candidateMisfireInstruction: ITrigger.EMisfireInstruction?): Boolean {
        return when (candidateMisfireInstruction) {
            ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY, ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_SMART_POLICY, ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW, ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT, ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT, ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT, ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT -> true
            else -> false
        }
    }

    /**
     *
     *
     * Updates the `SimpleTrigger`'s state based on the
     * MISFIRE_INSTRUCTION_XXX that was selected when the
     * `SimpleTrigger` was created.
     *
     *
     *
     * If the misfire instruction is set to MISFIRE_INSTRUCTION_SMART_POLICY, then
     * the following scheme will be used: <br></br>
     *
     *
     *  * If the Repeat Count is `0`, then the instruction will be
     * interpreted as `MISFIRE_INSTRUCTION_FIRE_NOW`.
     *  * If the Repeat Count is `REPEAT_INDEFINITELY`, then the
     * instruction will be interpreted as
     * `MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT`.
     * **WARNING:** using
     * MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT with a trigger
     * that has a non-null end-time may cause the trigger to never fire again if
     * the end-time arrived during the misfire time span.
     *  * If the Repeat Count is `> 0`, then the instruction will
     * be interpreted as
     * `MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT`.
     *
     *
     */
    override fun updateAfterMisfire(cal: Calendar) {
        /*var instr: ITrigger.EMisfireInstruction = misfireInstruction
        if (instr == ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_SMART_POLICY) {
            // What is smart
            instr = if (repeatCount == 0) ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW
            else if (repeatCount == REPEAT_INDEFINITELY) ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
            else {
                // if (getRepeatCount() > 0)
                ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
            }
        } else if (instr == ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW && repeatCount != 0) {
            instr =
                ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
        }

        when (instr) {
            ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY -> return
            ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW -> {
                setNextFireTime(Instant())
            }

            ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT -> {
                var newFireTime: Instant? = getFireTimeAfter(Instant())
                while (newFireTime != null && cal != null && !cal.isTimeIncluded(newFireTime.getTime())) {
                    newFireTime = getFireTimeAfter(newFireTime)

                    if (newFireTime == null) break

                    // avoid infinite loop
                    val c: java.util.Calendar = PDTFactory.createCalendar()
                    c.setTime(newFireTime)
                    if (c.get(java.util.Calendar.YEAR) > CQuartz.MAX_YEAR) {
                        newFireTime = null
                    }
                }
                setNextFireTime(newFireTime)
            }

            ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT -> {
                var newFireTime: Instant? = getFireTimeAfter(Instant())
                while (newFireTime != null && cal != null && !cal.isTimeIncluded(newFireTime.getTime())) {
                    newFireTime = getFireTimeAfter(newFireTime)

                    if (newFireTime == null) break

                    // avoid infinite loop
                    val c: java.util.Calendar = PDTFactory.createCalendar()
                    c.setTime(newFireTime)
                    if (c.get(java.util.Calendar.YEAR) > CQuartz.MAX_YEAR) {
                        newFireTime = null
                    }
                }
                if (newFireTime != null) {
                    val timesMissed = computeNumTimesFiredBetween(m_aNextFireTime, newFireTime)
                    timesTriggered = timesTriggered + timesMissed
                }

                setNextFireTime(newFireTime)
            }

            ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT -> {
                val newFireTime: Instant = Instant()
                if (m_nRepeatCount != 0 && m_nRepeatCount != REPEAT_INDEFINITELY) {
                    repeatCount = repeatCount - timesTriggered
                    timesTriggered = 0
                }

                if (endTime != null && endTime.before(newFireTime)) {
                    setNextFireTime(null) // We are past the end time
                } else {
                    setStartTime(newFireTime)
                    setNextFireTime(newFireTime)
                }
            }

            ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT -> {
                val newFireTime: Instant = Instant()

                val timesMissed = computeNumTimesFiredBetween(m_aNextFireTime, newFireTime)

                if (m_nRepeatCount != 0 && m_nRepeatCount != REPEAT_INDEFINITELY) {
                    var remainingCount = repeatCount - (timesTriggered + timesMissed)
                    if (remainingCount <= 0) {
                        remainingCount = 0
                    }
                    repeatCount = remainingCount
                    timesTriggered = 0
                }

                if (endTime != null && endTime.before(newFireTime)) {
                    setNextFireTime(null) // We are past the end time
                } else {
                    setStartTime(newFireTime)
                    setNextFireTime(newFireTime)
                }
            }

            ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_SMART_POLICY -> TODO()
            ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_DO_NOTHING -> TODO()
        }*/
    }

    /**
     * Called when the `[com.helger.quartz.IScheduler]` has
     * decided to 'fire' the trigger (execute the associated `Job`), in
     * order to give the `Trigger` a chance to update itself for its
     * next triggering (if any).
     *
     * @see .executionComplete
     */
    override fun triggered(calendar: Calendar) {
        /*timesTriggered++
        m_aPreviousFireTime = m_aNextFireTime
        m_aNextFireTime = getFireTimeAfter(m_aNextFireTime)

        while (m_aNextFireTime != null && calendar != null && !calendar.isTimeIncluded(
                m_aNextFireTime.getTime()
            )
        ) {
            m_aNextFireTime = getFireTimeAfter(m_aNextFireTime)

            if (m_aNextFireTime == null) break

            // avoid infinite loop
            val c: java.util.Calendar = PDTFactory.createCalendar()
            c.setTime(m_aNextFireTime)
            if (c.get(java.util.Calendar.YEAR) > CQuartz.MAX_YEAR) {
                m_aNextFireTime = null
            }
        }*/
    }

    /**
     * @see AbstractTrigger.updateWithNewCalendar
     */
    override fun updateWithNewCalendar(calendar: Calendar, misfireThreshold: Long) {
        /*m_aNextFireTime = getFireTimeAfter(m_aPreviousFireTime)

        if (m_aNextFireTime == null || calendar == null) {
            return
        }

        val now: Instant = Instant()
        while (m_aNextFireTime != null && !calendar.isTimeIncluded(m_aNextFireTime.getTime())) {
            m_aNextFireTime = getFireTimeAfter(m_aNextFireTime)

            if (m_aNextFireTime == null) break

            // avoid infinite loop
            val c: java.util.Calendar = PDTFactory.createCalendar()
            c.setTime(m_aNextFireTime)
            if (c.get(java.util.Calendar.YEAR) > CQuartz.MAX_YEAR) {
                m_aNextFireTime = null
            }

            if (m_aNextFireTime != null && m_aNextFireTime.before(now)) {
                val diff: Long = now.getTime() - m_aNextFireTime.getTime()
                if (diff >= misfireThreshold) {
                    m_aNextFireTime = getFireTimeAfter(m_aNextFireTime)
                }
            }
        }*/
    }

    /**
     *
     *
     * Called by the scheduler at the time a `Trigger` is first added
     * to the scheduler, in order to have the `Trigger` compute its
     * first fire time, based on any associated calendar.
     *
     *
     *
     * After this method has been called, `getNextFireTime()` should
     * return a valid answer.
     *
     *
     * @return the first time at which the `Trigger` will be fired by
     * the scheduler, which is also the same value
     * `getNextFireTime()` will return (until after the first
     * firing of the `Trigger`).
     */
    override fun computeFirstFireTime(calendar: Calendar): Instant? {
        /*m_aNextFireTime = startTime

        while (m_aNextFireTime != null && calendar != null && !calendar.isTimeIncluded(
                m_aNextFireTime.getTime()
            )
        ) {
            m_aNextFireTime = getFireTimeAfter(m_aNextFireTime)

            if (m_aNextFireTime == null) break

            // avoid infinite loop
            val c: java.util.Calendar = PDTFactory.createCalendar()
            c.setTime(m_aNextFireTime)
            if (c.get(java.util.Calendar.YEAR) > CQuartz.MAX_YEAR) {
                return null
            }
        }

        return m_aNextFireTime*/
        TODO()
    }

    /**
     *
     *
     * Returns the next time at which the `Trigger` is scheduled to
     * fire. If the trigger will not fire again, `null` will be
     * returned. Note that the time returned can possibly be in the past, if the
     * time that was computed for the trigger to next fire has already arrived,
     * but the scheduler has not yet been able to fire the trigger (which would
     * likely be due to lack of resources e.g. threads).
     *
     *
     *
     * The value returned is not guaranteed to be valid until after the
     * `Trigger` has been added to the scheduler.
     *
     *
     * @see com.helger.quartz.TriggerUtils.computeFireTimesBetween
     */
    /*override fun getNextFireTime(): Instant? {
        return m_aNextFireTime
    }*/

    /**
     * Returns the previous time at which the `SimpleTrigger` fired. If
     * the trigger has not yet fired, `null` will be returned.
     */
    /*override fun getPreviousFireTime(): Instant? {
        return m_aPreviousFireTime
    }*/

    /**
     * Set the next time at which the `SimpleTrigger` should fire.<br></br>
     * **This method should not be invoked by client code.**
     */
    /*override fun setNextFireTime(nextFireTime: Instant) {
        this.nextFireTime = nextFireTime
    }*/

    /**
     * Set the previous time at which the `SimpleTrigger` fired.<br></br>
     * **This method should not be invoked by client code.**
     */
    /*override fun setPreviousFireTime(previousFireTime: Instant) {
        this.previousFireTime = previousFireTime
    }*/

    /**
     *
     *
     * Returns the next time at which the `SimpleTrigger` will fire,
     * after the given time. If the trigger will not fire after the given time,
     * `null` will be returned.
     *
     *
     * @param aAfterTime
     * After time. May be `null`
     */
    override fun getFireTimeAfter( aAfterTime: Instant): Instant? {
        /*if ((timesTriggered > m_nRepeatCount) && (m_nRepeatCount != REPEAT_INDEFINITELY)) return null

        var afterTime: Instant? = aAfterTime
        if (afterTime == null) afterTime = Instant()

        if (m_nRepeatCount == 0 && afterTime.compareTo(startTime) >= 0) return null

        val startMillis: Long = startTime.getTime()
        val afterMillis: Long = afterTime.getTime()
        val endMillis = if ((endTime == null)) Long.MAX_VALUE else endTime.getTime()

        if (endMillis <= afterMillis) return null

        if (afterMillis < startMillis) return Instant(startMillis)

        val numberOfTimesExecuted = ((afterMillis - startMillis) / m_nRepeatInterval) + 1

        if ((numberOfTimesExecuted > m_nRepeatCount) && (m_nRepeatCount != REPEAT_INDEFINITELY)) return null

        val time: Instant =
            Instant(startMillis + (numberOfTimesExecuted * m_nRepeatInterval))
        if (endMillis <= time.getTime()) return null
        return time*/
        TODO()
    }

    /**
     *
     *
     * Returns the last time at which the `SimpleTrigger` will fire,
     * before the given time. If the trigger will not fire before the given time,
     * `null` will be returned.
     *
     */
     fun getFireTimeBefore(end: Instant): Instant? {
        /*if (end.getTime() < startTime.getTime()) {
            return null
        }

        val numFires = computeNumTimesFiredBetween(startTime, end)

        return Instant(startTime.getTime() + (numFires * m_nRepeatInterval))*/
        TODO()
    }

    fun computeNumTimesFiredBetween(start: Instant, end: Instant): Int {
        /*if (m_nRepeatInterval < 1) {
            return 0
        }

        val time: Long = end.getTime() - start.getTime()

        return (time / m_nRepeatInterval).toInt()*/
        TODO()
    }

    override val finalFireTime: Instant?
        /**
         *
         *
         * Returns the final time at which the `SimpleTrigger` will fire,
         * if repeatCount is REPEAT_INDEFINITELY, null will be returned.
         *
         *
         *
         * Note that the return time may be in the past.
         *
         */
        get() {
            if (repeatCount == 0) {
                return startTime
            }

            if (repeatCount == REPEAT_INDEFINITELY) {
                return if ((endTime == null)) null else getFireTimeBefore(endTime!!)
            }

            val lastTrigger = startTime!! + (repeatCount * repeatInterval).toDuration(DurationUnit.MILLISECONDS)

            if ((endTime == null) || (startTime!! < endTime!!)) {
                return lastTrigger
            }
            return getFireTimeBefore(endTime!!)
        }

    /**
     *
     *
     * Determines whether or not the `SimpleTrigger` will occur again.
     *
     */
    override fun mayFireAgain(): Boolean {
        return (nextFireTime != null)
    }

    /**
     *
     *
     * Validates whether the properties of the `JobDetail` are valid
     * for submission into a `Scheduler`.
     *
     * @throws IllegalStateException
     * if a required property (such as Name, Group, Class) is not set.
     */
    @Throws(SchedulerException::class)
    override fun validate() {
        super.validate()

        if (repeatCount != 0 && repeatInterval < 1) {
            throw SchedulerException("Repeat Interval cannot be zero.")
        }
    }

    override val scheduleBuilder: IScheduleBuilder<SimpleTrigger>
        /**
         * Get a [IScheduleBuilder] that is configured to produce a schedule
         * identical to this trigger's schedule.
         *
         * @see .getTriggerBuilder
         */
        get() {
            val sb: SimpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInMilliseconds(repeatInterval)
                .withRepeatCount(repeatCount)
            when (misfireInstruction) {
                ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW -> sb.withMisfireHandlingInstructionFireNow()
                ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT -> sb.withMisfireHandlingInstructionNextWithExistingCount()
                ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT -> sb.withMisfireHandlingInstructionNextWithRemainingCount()
                ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT -> sb.withMisfireHandlingInstructionNowWithExistingCount()
                ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT -> sb.withMisfireHandlingInstructionNowWithRemainingCount()
                ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY -> TODO()
                ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_SMART_POLICY -> TODO()
                ITrigger.EMisfireInstruction.MISFIRE_INSTRUCTION_DO_NOTHING -> TODO()
                null -> TODO()
            }
            return sb
        }




    companion object {
        
        fun create(
            name: String?,
            group: String?,
            jobName: String?,
            jobGroup: String?,
            startTime: Instant,
            endTime: Instant,
            repeatCount: Int,
            repeatInterval: Long
        ): SimpleTrigger {
            val ret = SimpleTrigger()
            ret.name = name
            ret.group = group
            ret.jobName = jobName
            ret.jobGroup = jobGroup
            ret.startTime=startTime
            ret.endTime = endTime
            ret.repeatCount = repeatCount
            ret.repeatInterval = repeatInterval
            return ret
        }
    }
}
